上週在操作 Conda 的時候,不小心把系統的 Python3 炸了,所以我花了一點時間處理 Linux 系統的問題,然後吃晚餐又不小心食物中毒,所以也就這樣斷賽並且延宕了不少進度,因此接下來就只能慢慢補回來囉!
在前一章中,我們提到了 CSS 1 過渡到 CSS 2 的過程,這轉變出自於當時的 CSS 1 在實做上的諸多限制、以及 IE 與 Ntescape 之間的戰爭。但網頁設計的需求也隨著時間推移變得越來越複雜,CSS 1 在實際應用上顯現出許多不足之處,特別是在版面設計的控制、多平台(裝置)支援的能力、和瀏覽器之間的相容性等方面。也正因因此讓 W3C 推出 CSS 2 以作為 CSS 1 的擴充版本來試圖解決這些問題。那接下來,就會來探討 CSS 2 在語法上做了哪些擴充。
在前面我們提到,1990 年代末期時的瀏覽器市場的競爭以 Netscape 與 IE 之間的角力最為激烈。這被稱為**「瀏覽器戰爭」**不僅是在搶佔市佔率,而是更延伸到技術層面各自推出專屬技術,也因此冷落了 W3C 所制定的網頁標準,其中就包含 CSS。
Netscape 希望透過屬於自己的 Layer(圖層)技術來統治網頁設計,而 Microsoft 則試圖推廣 ActiveX 和 VBScript。結果,這兩家公司的對抗讓就直接讓 CSS 1 成為一個「被孤立」的標準,設計師只能勉強在 IE 和 Netscape 之間找尋平衡,甚至為不同的瀏覽器寫兩套樣式表
這對網頁工程師來說就是非常棘手的事情了,他們為了讓網頁能在不同瀏覽器上正常顯示,就得量身打造 CSS,例如 Netscape Navigator 推出了如 Netscape Layer 等專屬功能,而 IE 則是大力推廣ActiveX 之類的技術,這光看了就覺得挺麻煩的。
這局面也讓 W3C 十分頭痛,因此 CSS 2 的主要目標就是要統一瀏覽器之間的相容性問題,不再受到個別瀏覽器的牽制,所以這理念就貫穿了整個 CSS 2 的設計過程:「制定統一標準使得不同瀏覽器的渲染行為趨於一致。」
然而在查閱 W3C 在 1998 年的官方文件後,你就會發現在 CSS 2.1 與 2 幾乎繼承了 CSS 1 的語法和功能,幾乎沒有什麼語法被標記為 Deprecated(廢棄)。因此我們可以將 CSS 2 視為解決 CSS 1 語法不足的擴充版本;而 CSS 2.1 亦為 CSS 2 的擴充版本。而是事實上每一代 CSS 都是如此,很多在 HTML 中被棄用的樣式語法都跑來 CSS 中了。上一篇文末有提到 CSS 1 對網頁工程師造成的困擾,因此 CSS 2.1 便有針對以下進行改進:
在早期網頁設計的圖文排版一直都是棘手的問題,當時只能用 <table>
插入圖片跟表格,但在 CSS 2 中就引入了 float
屬性讓文章能和其他內容可以圍繞元素浮動到兩側。基本上這就解決了當時的 HTML 表格佈局,讓畫面簡單多了。
img {
float: left;
}
在你處理 float
之後,如果你沒有處理下一個元素,就可能會卡在浮動的元素旁邊。因此這時候你就要用 clear
屬性來清除浮動的影響,讓下一個元素可以回到正常的排版中顯示。
p {
clear: both;
}
CSS 2 主要是增加一些對字體的控制:
font-variant
能將字母顯示為小型大寫字母(small caps),主要在顯示期刊文章或新聞文章時,會常用來強調一些標題或重要段落。
p {
font-variant: small-caps;
}
font-stretch
能對字體進行水平方向的壓縮或延伸,可以在你要設計頁面佈局,能將字體稍微拉長或壓縮,就不須要用插入圖片來達成這效果了。
p {
font-stretch: expanded;
}
字體屬性縮寫:font
屬性是 CSS 2 的0縮寫語法之一,你可以把 font-family
、font-size
、font-weight
和 font-style
寫在同一行。
p {
font: bold 16px Arial, sans-serif;
}
然而,並非所有的字體屬性都能適應時代的發展需求。例如,CSS 1 中的 text-decoration: blink
屬性曾經讓文字可以閃爍,雖然這在當時是一個有趣的特效,但在現代瀏覽器中,這種效果已經逐漸被棄用,因為它被認為對使用者體驗不佳。
在當年的網頁設計中,背景圖片呈現的設計是創造視覺效果的重要方式之一。在 CSS 1 時期的網頁工程師只能單純呈現背景圖片,你只能在一個超長內文的網頁中設計一個相對長的圖片來處理、或是直接設定為單色調或留白,但在 CSS 2 就是可以放一張符合螢幕解析度(例如 1280*960)的圖片就可以了:
background-attachment
可以設定背景圖片是保持固定、還是隨著頁面滾動。在當年這可是網頁工程師炫技的手法之一。
body {
background-attachment: fixed;
}
那如果你的圖片比較小,須要讓背景圖片可以重複顯示並寫填滿整個頁面,這時就可以用 background-repeat
來設定了
body {
background-repeat: no-repeat;
}
CSS 2 直接解決了當年 Box Model 在不同瀏覽器中實現的差異,而你也可以把定義邊界的語法寫在同一行裡面
margin
和 padding
的細緻控制:在 CSS 2.1 中,網頁工程師可以為每個方向(依序為上、右、下、左)單獨設定邊外邊距離和內框距離,你就發現在邊界控制上就可以很精確了。同時padding
和 border
不再計入元素的總寬度,所以布局上就可以針對需求進行逐項控制了。
p {
width: 300px;
margin: 10px 20px 30px 40px;
padding: 40px 30px 20px 10px;
border: 5px solid black;
}
除此之外,CSS 2.1 還對 overflow
、visibility
、以及 white-space
這三個屬性進行強化,其中 overflow
主要用來控制當內容超出容器邊界時的處理方式; visibility
則是用來顯示或隱藏一些元素;而 white-space
用來設定文章是否要強制換行。
div {
overflow: scroll;
visibility: hidden;
}
p {
white-space: nowrap;
}
CSS 2 大幅強化了表格的樣式控制,針對 CSS1 在呈現上的不一致進行修正。table-layout
在處理多行或多列資料時,可以解決因為 <table>
的預設行為導致不同裝置或瀏覽器在顯示上的差異;而 table-layout: fixed
就可以固定行寬;此外 border-collapse
和 border-spacing
可以控制表格邊框和每一格(Grid / Cell)的間距。
table {
border-collapse: collapse;
border-spacing: 5px;
table-layout: fixed;
}
有時候我們會希望一些內容被呈現在固定的地方,因此 CSS 2 推出了全新的 Positioning Model,定義 position
屬性來控制一個元素的顯示位置,可以分為絕對定位(absolute
)、相對定位(relative
)和固定定位(fixed
),這樣就擺脫靠表格處理的痛苦過程了。
div {
position: absolute;
top: 50px;
left: 100px;
}
p {
position: relative;
top: -15px;
right: 5px;
}
很多時候,你會發現一些老舊網頁的內容,在現今的大螢幕下,會出現間隔過大的問題,那現在的網頁教不會出現這情況,是因為排版功能幫我們把內容控制在一個範圍裡面,包含寬度(min-width
和 max-width
)與高度(min-height
和 max-height
)。這樣內容在內容較多或少時、或是螢幕過大時,可以不讓網頁呈現直接走樣,這也是 CSS 3 裡面做互動設計中的重點基礎。
div {
min-width: 200px;
max-width: 500px;
min-height: 100px;
max-height: 300px;
}
當年會使用瀏覽器的裝置逐漸不只是瀏覽器,當年歐美很流行的 PDA 與一些印表機裝置等,甚至是早期的語音瀏覽器都會被用來上網。在當時的 CSS 1 是靜態的,這些設備查看網頁時就只能用電腦版呈現的網頁,當時只能靠單獨設計專屬的網頁架構、或使用大量的 JavaScript 來解決這問題。
直到 CSS 2 引入了 Media Queries(媒體查詢)的語法 @media
,讓網頁工程師可以根據不同的裝置設定不同的顯示樣式,這樣你就可以用同一組 CSS 針對不同的裝置來設計 CSS 內的參數即可。例如當使用者要列印網頁時,網頁工程師可以選擇隱藏導航欄、圖片等不必要的元素等。
@media print {
body {
font-size: 12px;
}
}
在 CSS 1 時代的選擇器使用範圍非常有限,設計師只能透過幾種基礎選擇器來操作網頁上的元素。當時能用的選擇器僅包含:
p
或 h1
。.
符號來選擇帶有特定 class 屬性的元素,例如 .title
。#
符號來選擇唯一的 ID 屬性元素,例如 #header
。h1, h2, h3 { color: blue; }
。即便是這幾種簡單的選擇器,在當時卻讓設計師感到非常吃力。你可以想像,如果要針對同一類型標籤中的某些特定元素設定樣式,你會發現根本無法精確操作,設計師只能想盡辦法在每個元素中手動增加 class 或 ID,才能勉強達成目的。
舉個例子,如果你想改變列表中第二個項目的背景顏色,你會發現自己無從下手,因為 CSS1 根本沒有針對特定順序的選擇器,只能使用類似 <li class="second-item">
這種方式來標記每個元素。當列表項目很多時(例如 20 個),這樣的做法沒意外就是將 樣式 與 結構 全擠在一塊,你過一陣子回來看就會發現看不懂自己的文章了。也正因如此, CSS 2 主要就針對以下三個進行強化
Structural Pseudo-classes(結構性偽類選擇器)讓我們可以直接控制要改變的樣式。
例如你可以用 :first-child
來選取父容器中第一個子元素。這讓你就不需要再手動給第一個元素加上特定 class 或 ID,就能針對第 n 個元素進行樣式調整。例如:
p:first-child {
font-weight: bold;
}
/* Or alternatively for the (n)-th child: */
li:nth-child(even) {
background-color: lightgray;
}
你可以發現這個 Selector 基本上直接改變你處理列表的邏輯、無論是表格和多行段落的方式,你都可以針對每個奇數項還是偶數項設定不同樣式,一切都變得十分簡單。
Attribute Selectors(屬性選擇器)
這一變化對於表單元素(如 input
)的操作來說會變得相當便利。舉例來說,一個 基本屬性選擇器 可以選取擁有特定屬性的元素。例如你可以只選取所有 type="text"
的輸入框,而其他類型的輸入框就不會被影響;或是僅選取所有 href
屬性中有包含 "example" 的網址的元素:
input[type="text"] {
border: 1px solid black;
}
a[href*="example"] {
color: blue;
}
Psuedo-classes 與 Psuedo-elements(偽類與偽元素)
當時我看到這名詞的時候,真的是滿頭問號,想說這要「偽」什麼?後來才發現「虛擬類別」可能會比較好理解。這名詞是來自於,你會希望使用者執行一些行為時,可以對特定狀態的元素或特定位置進行樣式控制,但不是真正產生新的 Class 或 Element。也就是說,這些「虛構的」 Class 或 Elements 是直接基於現有元素上直接改變,而不必去改變 HTML 的結構,也不需要另外用 JavaScript 去控制。
:hover
、:focus
、:active
:這些 Psuedo Selector 讓可以讓你在一些使用者操作上進行樣式改變。舉例來說,當使用者的滑鼠游標停留在連結上時,連結的顏色就能自動改變,而且不需要額外撰寫 JavaScript 去控制了。
例如:
a:hover {
color: red;
}
input:focus {
border: 2px solid blue;
}
::before
和 ::after
:這就是在元素的前後插入一些元素,而你不用改變 HTML 結構。舉例來說,你可以在一些超連結前面加入一些 Icon 等。
p::before {
content: "Start:";
font-weight: bold;
}
p::after {
content: " End";
font-style: italic;
}
button:hover::before {
content: "👉 ";
}
button:hover::after {
content: " 👈";
}
在你實做之後就會發現,Psuedo-class 的呈現就是我們習以為常的視覺技術,我們不需要撰寫太多的 JavaScript 就可以讓網頁使用上有很大的視覺效果。
當年 CSS 2 規範中引入的「伺服器端與語音樣式」功能,當時實際上是設計給不同類型的設備用的,包括 PDA、印表機、以及語音瀏覽器,主要是讓網站可以將網站轉換為語音後朗讀出來。開發者可以針對語音設備(例如合成器)來調整內文被閱讀的順序、語速、音量…等。但由於瀏覽器的支援度較低,且這些語音設備還沒有被普及,所以大部分的語音屬性在 CSS 2.1 時就被移除了。
你會發現 CSS 跟 HTML 就是相輔相成的,而且 W3C 也在這過程中盡可能分離「架構」與「樣式」之間的分工,所以我們在實務操作上就會盡可能分離這些內容。而你在開發過程中,就會發現有些樣式會經常被重複使用到,你就會想要如何讓這些過程便得更加簡單?那我們就會用「框架」了,例如 Bootstrap 或 Tailwind。但我不建議一開始就學框架,因為你要知道原始的 CSS 是如何被架構出來的、而後才會知道框架可以直接幫我們解決哪些問題,在使用框架遇到一些問題後,才會知道如何正確的 Debug。總之,後續我們就會開始正式實做了。